feat(incentives): add interface#1678
feat(incentives): add interface#16780xClandestine merged 1 commit intorelease-dev/incentive-councilfrom
Conversation
| /// @notice Removes a distribution. | ||
| /// @dev Only the Incentive Council can call this function. | ||
| /// @dev Ref: Implied by "updateDistribution" and general management of distributions. | ||
| /// @param index The index of the distribution to remove. | ||
| function removeDistribution(uint256 index) external; |
There was a problem hiding this comment.
Open question: Should this mark the distribution as "removed" or outright remove the distribution from storage. Intuition tells me we should simply mark it as removed so it can still be easily audited/introspected in the future (and avoids need for swap/pop enumerable set pattern).
There was a problem hiding this comment.
Thinking implementation wise, we simply set the weight of the distribution to mark it cancelled, alternatively can just have another mapping.
| RewardsForAllEarners, | ||
| OperatorSetTotalStake, | ||
| OperatorSetUniqueStake, | ||
| EigenDA, |
There was a problem hiding this comment.
Isn't EigenDA just one of the old reward types? createAVSRewardsSubmission
There was a problem hiding this comment.
My interpretation is the intent of that ref is to give the possible types of rewards submissions that can be made? Could be wrong though
There was a problem hiding this comment.
We discuss the approach here in this doc (reference)
There was a problem hiding this comment.
Gotcha. If this becomes too complex in the EmissionsController, it might be worth just adding another createAVSRewardsSubmission with the AVS parameter in the RewardsCoordinator. There would be no sidecar changes here
There was a problem hiding this comment.
And we'd use the exact same event/logic. Don't think it's unreasonable bloat
There was a problem hiding this comment.
Well there would be a sidecar change in this case due to the additional parameter right?
| struct Distribution { | ||
| uint256 weight; | ||
| DistributionType distributionType; | ||
| bytes strategiesAndMultipliers; |
There was a problem hiding this comment.
Not an array? Have used that in the past
| /// @notice Removes a distribution. | ||
| /// @dev Only the Incentive Council can call this function. | ||
| /// @dev Ref: Implied by "updateDistribution" and general management of distributions. | ||
| /// @param index The index of the distribution to remove. | ||
| function removeDistribution(uint256 index) external; |
| /// @dev Ref: "Incentive Council Functions: addDistribution(weight{int}, distribution-type{see below}, strategiesAndMultipliers())" | ||
| /// @param weight The weight of the distribution. | ||
| /// @param distributionType The type of distribution. | ||
| /// @param strategiesAndMultipliers Encoded strategies and multipliers. |
There was a problem hiding this comment.
Will there be any error handling on the weight? I assume it's a proportion in BPS?
There was a problem hiding this comment.
Yeah should've specified in comments, weights are in bips (since easier to manually write out vs wad).
| } | ||
|
|
||
| /// @notice Configuration for the EmissionsController. | ||
| /// @dev Ref: "The amount of EIGEN minted weekly (inflation rate) is set by governance..." |
There was a problem hiding this comment.
So my understanding of the ELIP is that we have:
- A top-level inflation rate (eg 8%)
- Either eth-directed, Eigen-directed, or discretionary reward (eg. x% to ETH)
- A per-DistributionType type of above. x1% to rewards all stakers
How is 2 specified? It seems like we only do 3?
There was a problem hiding this comment.
2 is no longer specified. Just doing (3) is fine.
The intention is that the Incentive Council has a single bucket that it pulls from to direct emissions using the strategies and multipliers from Distribution. If there are additional commitments to be made around proportions of supply, they have to be encoded offchain or using strats and mults and the total values that end up being emitted.
| /// @notice Triggers the weekly emissions. | ||
| /// @dev Ref: "The ActionGenerator today is a contract ... that is triggered by the Hopper. When triggered, it mints new EIGEN tokens..." | ||
| /// @dev Permissionless function that can be called by anyone when `canPress()` returns true. | ||
| function pressButton() external; |
There was a problem hiding this comment.
We might have to batch this if the size of the distribution array becomes too large... something to think about for implementation
There was a problem hiding this comment.
Good callout, yeah will need to ensure the button is always "pressable".
There was a problem hiding this comment.
Added a comment noting we should use pagination to avoid DOS/OOG.
| /// @notice Triggers the weekly emissions. | ||
| /// @dev Ref: "The ActionGenerator today is a contract ... that is triggered by the Hopper. When triggered, it mints new EIGEN tokens..." | ||
| /// @dev Permissionless function that can be called by anyone when `canPress()` returns true. | ||
| function pressButton() external; |
There was a problem hiding this comment.
Also, what happens if one of the many reward tx's fail?
There was a problem hiding this comment.
Do we need a try-catch for that or do we ensure it doesn't fail at all? How does the current Hopper handle that?
There was a problem hiding this comment.
@MC1823315 calls this out... Try-Catch (or error handling) is necessary to ensure minting does not revert (push button succeeds).
There was a problem hiding this comment.
We need to make sure that wallets will properly estimate this. We've had a bunch of problems with slashing when it came to using a try-catch
There was a problem hiding this comment.
Is this relevant for pressButton() though? Shouldn't this primarily be called by a cronjob that can set an arbitrarily large gas limit?
| /// @notice Configuration for the EmissionsController. | ||
| /// @dev Ref: "The amount of EIGEN minted weekly (inflation rate) is set by governance..." | ||
| struct EmissionsConfiguration { | ||
| uint256 inflationRate; |
There was a problem hiding this comment.
This will be a global fixed value not settable by the Incentives Council, only by Protocol Council ELIP. settable below.
There was a problem hiding this comment.
They're now constant values, only modifiable via upgrades.
| struct EmissionsConfiguration { | ||
| uint256 inflationRate; | ||
| uint256 startTime; | ||
| uint256 cooldownSeconds; |
There was a problem hiding this comment.
These will also be global values that only allow the button to be pressed once a week on emissions. They should explicitly not be settable.
There was a problem hiding this comment.
They're now constant values, only modifiable via upgrades.
ypatil12
left a comment
There was a problem hiding this comment.
LGTM. Just added some clarifying comments
| OperatorSetTotalStake, | ||
| OperatorSetUniqueStake, | ||
| EigenDA, | ||
| Manual |
There was a problem hiding this comment.
What is manual? Just directed to anyone?
There was a problem hiding this comment.
Ref: "Manual Distribution - Rewards that are sent directly to the Incentive Council multisig for manual distribution."
| RewardsForAllEarners, | ||
| OperatorSetTotalStake, | ||
| OperatorSetUniqueStake, | ||
| EigenDA, |
There was a problem hiding this comment.
Gotcha. If this becomes too complex in the EmissionsController, it might be worth just adding another createAVSRewardsSubmission with the AVS parameter in the RewardsCoordinator. There would be no sidecar changes here
| RewardsForAllEarners, | ||
| OperatorSetTotalStake, | ||
| OperatorSetUniqueStake, | ||
| EigenDA, |
There was a problem hiding this comment.
And we'd use the exact same event/logic. Don't think it's unreasonable bloat
|
This should also target a release branch. |
|
|
||
| /// @title IEmissionsControllerEvents | ||
| /// @notice Events for the IEmissionsController contract. | ||
| interface IEmissionsControllerEvents is IEmissionsControllerTypes { |
There was a problem hiding this comment.
Need an event for pressButton().
There was a problem hiding this comment.
Will add an event with the implementation.
1564be7 to
1103809
Compare
10fed32 to
d7dfd40
Compare
IEmissionsController1103809 to
9e0aee8
Compare
d7dfd40 to
0acd8a2
Compare
9883f5b to
30bc2aa
Compare
**Motivation:** We need approval on initial design of the upcoming incentives council feature release. Historically, we've used [EigenHopper](https://github.com/Layr-Labs/EigenHopper/tree/master) for token emissions. Now we're looking to make some improvements. **Modifications:** - Drafted a consolidated interface that combines the two `EigenHopper` contracts into a single new contract. **Result:** Single consolidated interface with hopper functionality and proposed ELIP-012 functionality.
# v1.12.0 Incentive Council (ELIP-012) ## Release Manager @0xClandestine # Overview **Core Features** - **EmissionsController**: Mints EIGEN at a fixed inflation rate per epoch and distributes via gauge weights (bips 0-10,000) - **Permissionless Trigger**: Anyone can call `pressButton()` to process epoch emissions—no trusted keeper required - **5 Distribution Types**: - `RewardsForAllEarners` — Protocol-wide rewards for all delegated stake - `OperatorSetTotalStake` — Rewards proportional to total stake in operator set - `OperatorSetUniqueStake` — Rewards proportional to unique stake allocations - `EigenDA` — Special pathway for EigenDA (pre-OperatorSets AVS) - `Manual` — Off-chain computed distributions sent directly to Incentives Committee - **Silent Failure Handling**: Distribution failures don't block other distributions (except reentrancy/OOG attacks) - **Protocol Fee Mechanism**: Opt-in 20% fee on reward submissions in RewardsCoordinator - Disabled by default (backward compatible) - Submitters opt-in via `setOptInForProtocolFee()` - Fee recipient configurable by owner **Governance & Roles** - **Protocol Council**: Sets Incentives Committee address via `setIncentiveCouncil()` - **Incentives Committee**: - Configure distributions: `addDistribution()`, `updateDistribution()` - Receive swept tokens via `sweep()` - **AVSs**: Must grant EmissionsController permission for OperatorSet distributions **Key Design Points** - **Epoch-Based**: EIGEN minted once per epoch at `EMISSIONS_INFLATION_RATE` - **Future-Only Updates**: Distributions can only be added/updated for future epochs - **Immutable Config**: Inflation rate, start time, and epoch length set at deployment - **Pausable**: Both `pressButton()` and `sweep()` respect `PAUSED_TOKEN_FLOWS` flag - **Missed Epochs Skipped**: No accumulation—if `pressButton()` isn't called during an epoch, those emissions are permanently lost # Changelog - feat(incentives): add interface [#1678](#1678) - feat(incentives): add implementation [#1681](#1681) - feat(incentives): add protocol fee [#1691](#1691) - feat(incentives): add deploy scripts [#1699](#1699) - fix: internal review changes [#1703](#1703) - feat: add EigenDA rewards submission type [#1705](#1705) # Scope **New Contracts:** - `EmissionsController.sol` — Main implementation - `EmissionsControllerStorage.sol` — Storage layout - `IEmissionsController.sol` — Interface **Modified Contracts:** - `RewardsCoordinator.sol` — Added protocol fee mechanism and `createEigenDARewardsSubmission()` - `RewardsCoordinatorStorage.sol` — Added `PROTOCOL_FEE_BIPS`, `isOptedInForProtocolFee`, `feeRecipient`, `emissionsController` --------- Co-authored-by: Rajath Alex <rajathalex@gmail.com> Co-authored-by: eigenmikem <michael.muehl@eigenlabs.org> Co-authored-by: ELHAJ <124453227+elhajin@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
# v1.12.0 Incentive Council (ELIP-012) ## Release Manager @0xClandestine # Overview **Core Features** - **EmissionsController**: Mints EIGEN at a fixed inflation rate per epoch and distributes via gauge weights (bips 0-10,000) - **Permissionless Trigger**: Anyone can call `pressButton()` to process epoch emissions—no trusted keeper required - **5 Distribution Types**: - `RewardsForAllEarners` — Protocol-wide rewards for all delegated stake - `OperatorSetTotalStake` — Rewards proportional to total stake in operator set - `OperatorSetUniqueStake` — Rewards proportional to unique stake allocations - `EigenDA` — Special pathway for EigenDA (pre-OperatorSets AVS) - `Manual` — Off-chain computed distributions sent directly to Incentives Committee - **Silent Failure Handling**: Distribution failures don't block other distributions (except reentrancy/OOG attacks) - **Protocol Fee Mechanism**: Opt-in 20% fee on reward submissions in RewardsCoordinator - Disabled by default (backward compatible) - Submitters opt-in via `setOptInForProtocolFee()` - Fee recipient configurable by owner **Governance & Roles** - **Protocol Council**: Sets Incentives Committee address via `setIncentiveCouncil()` - **Incentives Committee**: - Configure distributions: `addDistribution()`, `updateDistribution()` - Receive swept tokens via `sweep()` - **AVSs**: Must grant EmissionsController permission for OperatorSet distributions **Key Design Points** - **Epoch-Based**: EIGEN minted once per epoch at `EMISSIONS_INFLATION_RATE` - **Future-Only Updates**: Distributions can only be added/updated for future epochs - **Immutable Config**: Inflation rate, start time, and epoch length set at deployment - **Pausable**: Both `pressButton()` and `sweep()` respect `PAUSED_TOKEN_FLOWS` flag - **Missed Epochs Skipped**: No accumulation—if `pressButton()` isn't called during an epoch, those emissions are permanently lost # Changelog - feat(incentives): add interface [#1678](#1678) - feat(incentives): add implementation [#1681](#1681) - feat(incentives): add protocol fee [#1691](#1691) - feat(incentives): add deploy scripts [#1699](#1699) - fix: internal review changes [#1703](#1703) - feat: add EigenDA rewards submission type [#1705](#1705) # Scope **New Contracts:** - `EmissionsController.sol` — Main implementation - `EmissionsControllerStorage.sol` — Storage layout - `IEmissionsController.sol` — Interface **Modified Contracts:** - `RewardsCoordinator.sol` — Added protocol fee mechanism and `createEigenDARewardsSubmission()` - `RewardsCoordinatorStorage.sol` — Added `PROTOCOL_FEE_BIPS`, `isOptedInForProtocolFee`, `feeRecipient`, `emissionsController`
Motivation:
We need approval on initial design of the upcoming incentives council feature release. Historically, we've used EigenHopper for token emissions. Now we're looking to make some improvements.
Modifications:
EigenHoppercontracts into a single new contract.Result:
Single consolidated interface with hopper functionality and proposed ELIP-012 functionality.